﻿
namespace HSS.Xaml.UI.Controls
{
	#region Using Directives
	using System;
	using System.Collections;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.ComponentModel;
	using System.Linq;
	using System.Windows;
	using System.Windows.Controls;
	using HSS.ComponentModel;
	#endregion

	#region IEditorDialog
	/// <summary>
	/// A dialog window to allow editing a Collection.
	/// </summary>
	public partial class CollectionEditorDialog : IEditorDialog
	{
		#region Fields

		internal static List<CollectionEditorDialog> currentInstances = new List<CollectionEditorDialog>();
		internal Type elementType;

		PropertyGridEntry _propertyGridEntry;
		IList originalTarget;
		bool isNew;
		bool isArray;

		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public CollectionEditorDialog()
		{
			if (!System.ComponentModel.DesignerProperties.IsInDesignTool)
				throw new System.Exception();
		}
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="target">The IList to expose.</param>
		/// <param name="targetElementType">The list's Element Type.</param>
		public CollectionEditorDialog(PropertyGridEntry propertyGridEntry, Type targetElementType)
		{
			Guard.ArgumentNotNull(targetElementType, "targetElementType");

			currentInstances.Add(this);
			this.Unloaded += new RoutedEventHandler(CollectionEditorDialog_Unloaded);

			InitializeComponent();

			this.PropertyGrid.ValueChanged += new EventHandler<EventArgs>(Descriptor_ValueChanged);

			this.elementType = targetElementType;
			this._propertyGridEntry = propertyGridEntry;
			this.Title = "Edit - " + propertyGridEntry.Name;
			this.isNew = null == propertyGridEntry.CurrentValue;
			this.isArray = propertyGridEntry.Descriptor.PropertyType.IsArray;
			if (this.isNew)
			{
				if (isArray)
					this.originalTarget = Array.CreateInstance(elementType, 0) as IList;
				else
					this.originalTarget = Activator.CreateInstance(propertyGridEntry.Descriptor.PropertyType) as IList;
			}
			else
			{
				this.originalTarget = propertyGridEntry.CurrentValue as IList;
			}


			var generic = typeof(ObservableCollection<>);
			if (elementType.IsValueType)
			{
				this.PropertyGrid.IsDescriptionVisible = false;
				var gen = typeof(ValueTypeItem<>);
				var type = gen.MakeGenericType(this.elementType);
				var listType = generic.MakeGenericType(type);
				this.InternalList = (IList)Activator.CreateInstance(listType);
				this.dtgCollection.ItemsSource = InternalList;
				foreach (var item in this.originalTarget)
				{
					var valueType = this.CreateValueTypeItem();
					SetValueTypeValue(valueType, item);
					this.InternalList.Add(valueType);
				}
			}
			else
			{
				this.PropertyGrid.DefaultLabelWidth = new GridLength(150);
				var listType = generic.MakeGenericType(this.elementType);
				this.InternalList = (IList)Activator.CreateInstance(listType);
				this.dtgCollection.ItemsSource = InternalList;
				foreach (var item in this.originalTarget)
					this.InternalList.Add(item);
			}

		}
		#endregion

		#region Properties
		private IList InternalList;
		#endregion

		#region Methods
		/// <summary>
		/// Ends the current edit operation.
		/// </summary>		
		public void EndEdit()
		{
			if (isArray)
			{
				var size = this.InternalList.Count;
				var arr = Array.CreateInstance(this.elementType, size);
				for (int i = 0; i < size; i++)
				{
					if (elementType.IsValueType)
						arr.SetValue(GetValueTypeValue(this.InternalList[i]), i);
					else
						arr.SetValue(this.InternalList[i], i);
				}
				this._propertyGridEntry.SetPropertyValue(arr);
				return;
			}

			if (!isNew)
			{
				this.originalTarget.Clear();
				foreach (var item in InternalList)
				{
					if (elementType.IsValueType)
						this.originalTarget.Add(GetValueTypeValue(item));
					else
						this.originalTarget.Add(item);
				}
			}
			else
			{
				foreach (var item in InternalList)
				{
					if (elementType.IsValueType)
						this.originalTarget.Add(GetValueTypeValue(item));
					else
						this.originalTarget.Add(item);
				}
				this._propertyGridEntry.SetPropertyValue(this.originalTarget);
			}
		}
		object CreateValueTypeItem()
		{
			var gen = typeof(ValueTypeItem<>);
			var type = gen.MakeGenericType(this.elementType);
			var instance = Activator.CreateInstance(type);
			return instance;
		}
		static void SetValueTypeValue(object valueTypeInstance, object value)
		{
			var valueProp = valueTypeInstance.GetType().GetProperty("Value");
			valueProp.SetValue(valueTypeInstance, value, null);
		}
		static object GetValueTypeValue(object valueTypeInstance)
		{
			var valueProp = valueTypeInstance.GetType().GetProperty("Value");
			return valueProp.GetValue(valueTypeInstance, null);
		}
		void ResetList()
		{
			var selected = dtgCollection.SelectedItem;
			this.dtgCollection.ItemsSource = null;
			this.dtgCollection.ItemsSource = this.InternalList;
			dtgCollection.SelectedItem = selected;
		}
		#endregion

		#region Event Handlers

		private void Descriptor_ValueChanged(object sender, EventArgs e)
		{
			this.ResetList();
		}

		private void CollectionEditorDialog_Unloaded(object sender, RoutedEventArgs e)
		{
			currentInstances.Remove(this);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		private void dtgCollection_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			if (e.AddedItems.Count > 0)
				this.PropertyGrid.SelectedObject = e.AddedItems[0];
			else
				this.PropertyGrid.SelectedObject = null;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		private void dtgCollection_LoadingRow(object sender, DataGridRowEventArgs e)
		{
			var row = e.Row;
			row.Header = new TextBlock { Text = (e.Row.GetIndex()).ToString(System.Globalization.CultureInfo.CurrentCulture), Margin = new Thickness(-1, 0, 2, 0) };
			(this.dtgCollection.Columns[0].GetCellContent(row) as TextBlock).SetValue(ToolTipService.ToolTipProperty, (row.DataContext).ToString());
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		private void btnOK_Click(object sender, RoutedEventArgs e)
		{
			this.DialogResult = true;
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void AddButton_Click(object sender, RoutedEventArgs e)
		{
			try
			{
				object newInstance = null;

				if (elementType.IsValueType)
				{
					newInstance = CreateValueTypeItem();
				}
				else
				{
					var constructor = elementType.GetConstructor(Type.EmptyTypes);
					if (constructor == null)
						throw new Exception("No default constructor found.");
					newInstance = constructor.Invoke(null);
				}
				this.InternalList.Add(newInstance);
				this.ResetList();
				this.dtgCollection.SelectedItem = newInstance;
			}
			catch (Exception ex)
			{
				if (null != ex.InnerException)
					new PropertyGridError(ex.InnerException.Message).Show();
				else
					new PropertyGridError(ex.Message).Show();
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void RemoveButton_Click(object sender, RoutedEventArgs e)
		{
			try
			{
				if (this.dtgCollection.SelectedIndex >= 0)
				{
					this.InternalList.Remove(this.dtgCollection.SelectedItem);
					this.ResetList();
					if (this.InternalList.Count > 0)
						this.dtgCollection.SelectedItem = this.InternalList[0];
				}
			}
			catch (Exception ex)
			{
				if (null != ex.InnerException)
					new PropertyGridError(ex.InnerException.Message).Show();
				else
					new PropertyGridError(ex.Message).Show();
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void MoveUpButton_Click(object sender, RoutedEventArgs e)
		{
			try
			{
				if (this.dtgCollection.SelectedIndex > 0)
				{
					var selected = dtgCollection.SelectedItem;
					var position = InternalList.IndexOf(selected);
					this.InternalList.Remove(selected);
					position--;
					this.InternalList.Insert(position, selected);
					this.ResetList();
					this.dtgCollection.SelectedItem = selected;
				}
			}
			catch (Exception ex)
			{
				if (null != ex.InnerException)
					new PropertyGridError(ex.InnerException.Message).Show();
				else
					new PropertyGridError(ex.Message).Show();
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void MoveDownButton_Click(object sender, RoutedEventArgs e)
		{
			try
			{
				if (this.dtgCollection.SelectedIndex < this.InternalList.Count - 1)
				{
					var selected = dtgCollection.SelectedItem;
					var position = this.InternalList.IndexOf(selected);
					this.InternalList.Remove(selected);
					position++;
					this.InternalList.Insert(position, selected);
					this.ResetList();
					this.dtgCollection.SelectedItem = selected;
				}
			}
			catch (Exception ex)
			{
				if (null != ex.InnerException)
					new PropertyGridError(ex.InnerException.Message).Show();
				else
					new PropertyGridError(ex.Message).Show();
			}
		}

		#endregion
	}
	#endregion

	#region ValueTypeItem
	/// <summary>
	/// ValueTypeItem
	/// </summary>
	/// <typeparam name="T">The ValueType Type.</typeparam>
	internal sealed class ValueTypeItem<T> : NotifiableObject
	{
		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		public ValueTypeItem()
		{

		}
		#endregion

		/// <summary>
		/// Gets or sets the item index.
		/// </summary>
		[System.ComponentModel.Browsable(false)]
		public int Index
		{
			get;
			set;
		}
		/// <summary>
		/// Gets or sets the Value.
		/// </summary>
		[ValueTypeCategory("ValueType")]
		public T Value
		{
			get { return _value; }
			set
			{
				_value = value;
				this.OnPropertyChanged(() => Value);
			}
		} T _value;
		/// <summary>
		/// Gets the current value as a string.
		/// </summary>
		/// <returns>The value as a string.</returns>
		public override string ToString()
		{
			return Value.ToString();
		}
	}
	#endregion

	#region ValueTypeCategoryAttribute
	/// <summary>
	/// ValueTypeCategoryAttribute
	/// </summary>
	[AttributeUsage(AttributeTargets.All)]
	internal sealed class ValueTypeCategoryAttribute : CategoryAttribute
	{
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="category">The category.</param>
		public ValueTypeCategoryAttribute(string category)
			: base(category)
		{

		}

		/// <summary>
		/// Gets a localized string.
		/// </summary>
		/// <param name="value">The value to localize.</param>
		/// <returns>The localized value.</returns>
		protected override string GetLocalizedString(string value)
		{
			var instance = CollectionEditorDialog.currentInstances.LastOrDefault();
			if (null != instance)
				return instance.elementType.Name;
			return value;
		}
	}
	#endregion
}